/*
* This software is subject to the terms of the Eclipse Public License v1.0
* Agreement, available at the following URL:
* http://www.eclipse.org/legal/epl-v10.html.
* You must accept the terms of that agreement to use this software.
*
* Copyright (c) 2002-2021 Hitachi Vantara..  All rights reserved.
*/

package mondrian.calc.impl;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import mondrian.calc.Calc;
import mondrian.calc.CalcWriter;
import mondrian.calc.ResultStyle;
import mondrian.mdx.NamedSetExpr;
import mondrian.mdx.ResolvedFunCall;
import mondrian.olap.Evaluator;
import mondrian.olap.Exp;
import mondrian.olap.Hierarchy;
import mondrian.olap.Member;
import mondrian.olap.type.Type;
import mondrian.rolap.RolapEvaluator;
import mondrian.rolap.RolapHierarchy;

/**
 * Abstract implementation of the {@link mondrian.calc.Calc} interface.
 *
 * @author jhyde
 * @since Sep 27, 2005
 */
public abstract class AbstractCalc implements Calc {
  private final Calc[] calcs;
  protected final Type type;
  protected final Exp exp;

  /**
   * Creates an AbstractCalc.
   *
   * @param exp
   *          Source expression
   * @param calcs
   *          Child compiled expressions
   */
  protected AbstractCalc( Exp exp, Calc[] calcs ) {
    assert exp != null;
    this.exp = exp;
    this.calcs = calcs;
    this.type = exp.getType();
  }

  public Type getType() {
    return type;
  }

  /**
   * {@inheritDoc}
   *
   * Default implementation just does 'instanceof TargetClass'. Subtypes that are wrappers should override.
   */
  public boolean isWrapperFor( Class<?> iface ) {
    return iface.isInstance( this );
  }

  /**
   * {@inheritDoc}
   *
   * Default implementation just casts to TargetClass. Subtypes that are wrappers should override.
   */
  public <T> T unwrap( Class<T> iface ) {
    return iface.cast( this );
  }

  public void accept( CalcWriter calcWriter ) {
    calcWriter.visitCalc( this, getName(), getArguments(), getCalcs() );
  }

  /**
   * Returns the name of this expression type, used when serializing an expression to a string.
   *
   * <p>
   * The default implementation tries to extract a name from a function call, if any, then prints the last part of the
   * class name.
   */
  protected String getName() {
    String name = lastSegment( getClass() );
    if ( isDigits( name ) ) {
      if ( exp instanceof ResolvedFunCall ) {
        ResolvedFunCall funCall = (ResolvedFunCall) exp;
        name = funCall.getFunDef().getName();
      } else if ( exp instanceof NamedSetExpr ) {
        // Expose name of nameset for explain plan
        NamedSetExpr nse = (NamedSetExpr) exp;
        name = nse.getNamedSet().getName();
      }
    }
    return name;
  }

  /**
   * Returns the last segment of a class name.
   *
   * <p>
   * Examples: lastSegment("com.acme.Foo") = "Foo" lastSegment("com.acme.Foo$Bar") = "Bar" lastSegment("com.acme.Foo$1")
   * = "1"
   *
   * @param clazz
   *          Class
   * @return Last segment of class name
   */
  private String lastSegment( Class clazz ) {
    final String name = clazz.getName();
    int dot = name.lastIndexOf( '.' );
    int dollar = name.lastIndexOf( '$' );
    int dotDollar = Math.max( dot, dollar );
    if ( dotDollar >= 0 ) {
      return name.substring( dotDollar + 1 );
    }
    return name;
  }

  private static boolean isDigits( String name ) {
    for ( int i = 0; i < name.length(); i++ ) {
      char c = name.charAt( i );
      if ( "0123456789".indexOf( c ) < 0 ) {
        return false;
      }
    }
    return true;
  }

  /**
   * Returns this expression's child expressions.
   */
  public Calc[] getCalcs() {
    return calcs;
  }

  public boolean dependsOn( Hierarchy hierarchy ) {
    return anyDepends( getCalcs(), hierarchy );
  }

  /**
   * Returns true if one of the calcs depends on the given dimension.
   */
  public static boolean anyDepends( Calc[] calcs, Hierarchy hierarchy ) {
    for ( Calc calc : calcs ) {
      if ( calc != null && calc.dependsOn( hierarchy ) ) {
        return true;
      }
    }
    return false;
  }

  /**
   * Returns true if calc[0] depends on dimension, else false if calc[0] returns dimension, else true if any of the
   * other calcs depend on dimension.
   *
   * <p>
   * Typical application: <code>Aggregate({Set}, {Value Expression})</code> depends upon everything {Value Expression}
   * depends upon, except the dimensions of {Set}.
   */
  public static boolean anyDependsButFirst( Calc[] calcs, Hierarchy hierarchy ) {
    if ( calcs.length == 0 ) {
      return false;
    }
    if ( calcs[0].dependsOn( hierarchy ) ) {
      return true;
    }
    if ( calcs[0].getType().usesHierarchy( hierarchy, true ) ) {
      return false;
    }
    for ( int i = 1; i < calcs.length; i++ ) {
      Calc calc = calcs[i];
      if ( calc != null && calc.dependsOn( hierarchy ) ) {
        return true;
      }
    }
    return false;
  }

  /**
   * Returns true if any of the calcs depend on dimension, else false if any of the calcs return dimension, else true.
   */
  public static boolean butDepends( Calc[] calcs, Hierarchy hierarchy ) {
    boolean result = true;
    for ( Calc calc : calcs ) {
      if ( calc != null ) {
        if ( calc.dependsOn( hierarchy ) ) {
          return true;
        }
        if ( calc.getType().usesHierarchy( hierarchy, true ) ) {
          result = false;
        }
      }
    }
    return result;
  }

  /**
   * Returns any other arguments to this calc.
   *
   * @return Collection of name/value pairs, represented as a map
   */
  protected final Map<String, Object> getArguments() {
    final Map<String, Object> argumentMap = new LinkedHashMap<>();
    collectArguments( argumentMap );
    return argumentMap;
  }

  /**
   * Collects any other arguments to this calc.
   *
   * <p>
   * The default implementation returns name, class, type, resultStyle. A subclass must call super, but may add other
   * arguments.
   *
   * @param arguments
   *          Collection of name/value pairs, represented as a map
   */
  protected void collectArguments( Map<String, Object> arguments ) {
    arguments.put( "name", getName() );
    arguments.put( "class", getClass() );
    arguments.put( "type", getType() );
    arguments.put( "resultStyle", getResultStyle() );
  }

  /**
   * Returns a simplified evalator whose context is the same for every dimension which an expression depends on, and the
   * default member for every dimension which it does not depend on.
   *
   * <p>
   * The default member is often the 'all' member, so this evaluator is usually the most efficient context in which to
   * evaluate the expression.
   *
   * @param calc
   * @param evaluator
   */
  public static Evaluator simplifyEvaluator( Calc calc, Evaluator evaluator ) {
    if ( evaluator.isNonEmpty() ) {
      // If NON EMPTY is present, we cannot simplify the context, because
      // we have to assume that the expression depends on everything.
      // TODO: Bug 1456418: Convert 'NON EMPTY Crossjoin' to
      // 'NonEmptyCrossJoin'.
      return evaluator;
    }
    int changeCount = 0;
    Evaluator ev = evaluator;
    final List<RolapHierarchy> hierarchies = ( (RolapEvaluator) evaluator ).getCube().getHierarchies();
    for ( RolapHierarchy hierarchy : hierarchies ) {
      final Member member = ev.getContext( hierarchy );
      if ( member.isAll() ) {
        continue;
      }
      if ( calc.dependsOn( hierarchy ) ) {
        continue;
      }
      final Member unconstrainedMember = member.getHierarchy().getDefaultMember();
      if ( member == unconstrainedMember ) {
        // This is a hierarchy without an 'all' member, and the context
        // is already the default member.
        continue;
      }
      if ( changeCount++ == 0 ) {
        ev = evaluator.push();
      }
      ev.setContext( unconstrainedMember );
    }
    return ev;
  }

  public ResultStyle getResultStyle() {
    return ResultStyle.VALUE;
  }

}

// End AbstractCalc.java
